package editor.widgets;

import editor.Images;
import editor.PwnIDE;
import editor.UIController;
import editor.code.LineField;
import editor.headers.Header;
import event.TextReplacementEvent;
import event.UndoSet;
import event.Undoable;
import model.caret.Caret;
import model.caret.CaretRef;
import model.caret.TextCaret;
import model.code.CodeFile;
import model.code.CodeFileChunk;
import model.code.line.LineList;
import model.code.line.MultiLineList;
import widgets.ImageButton;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;

/**
 * @author Neil Dickson
 */
public class FindBar extends JComponent {
	private UIController	ui;
	public final	LineField		findTextField;
	public final	LineField		replaceTextField;
	private final ActionListener	cellObserver;
	private ImageButton 	next;
	private ImageButton		prev;
	private ImageButton		all;
	private	ImageButton		replace;
	private	ImageButton		replaceAll;
	private	ImageButton		close;
	private JLabel			message;
	private Timer			messageTimer;
	private	ObservedBooleanCheckBox	wholeProjectBox;
	private	ObservedBooleanCheckBox	matchCaseBox;
	private	ObservedBooleanCheckBox	matchWordsBox;

	private	CaretRef		caretRef;

	public FindBar(UIController ui) {
		this.ui = ui;
		caretRef = new CaretRef();
		MultiLineList findLines = new MultiLineList(null, CodeFileChunk.LOCATION_NOWHERE);
		MultiLineList replaceLines = new MultiLineList(null, CodeFileChunk.LOCATION_NOWHERE);
		findTextField = new LineField(findLines,caretRef,true);
		replaceTextField = new LineField(replaceLines,caretRef,true);
		caretRef.set(new TextCaret(caretRef,findTextField.getLines(),0,0,0));
		setLayout(null);
		setBackground(Color.WHITE);
		findTextField.setBorder(Header.TEXT_FIELD_BORDER);
		add(findTextField);
		replaceTextField.setBorder(Header.TEXT_FIELD_BORDER);
		add(replaceTextField);
		next = new ImageButton(Images.FIND_NEXT_UP,Images.FIND_NEXT_HOVER,Images.FIND_NEXT_DOWN,Images.FIND_NEXT_DISABLED);
		next.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				goToNext();
			}
		});
		prev = new ImageButton(Images.FIND_PREV_UP,Images.FIND_PREV_HOVER,Images.FIND_PREV_DOWN,Images.FIND_PREV_DISABLED);
		prev.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				goToPrev();
			}
		});
		all = new ImageButton(Images.FIND_ALL_UP,Images.FIND_ALL_HOVER,Images.FIND_ALL_DOWN,Images.FIND_ALL_DISABLED);
		all.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				findAll();
			}
		});
		replace = new ImageButton(Images.REPLACE_UP,Images.REPLACE_HOVER,Images.REPLACE_DOWN,Images.REPLACE_DISABLED);
		replace.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				replaceText();
			}
		});
		replaceAll = new ImageButton(Images.REPLACE_ALL_UP,Images.REPLACE_ALL_HOVER,Images.REPLACE_ALL_DOWN,Images.REPLACE_ALL_DISABLED);
		replaceAll.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				replaceAllText();
			}
		});
		close = new ImageButton(Images.CLOSE_UP,Images.CLOSE_HOVER,Images.CLOSE_DOWN,Images.CLOSE_DISABLED);
		close.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				close();
			}
		});
		message = new JLabel();
		messageTimer = new Timer(4000,new ActionListener(){
			public void actionPerformed(ActionEvent e) {
				message.setText("");
			}
		});
		messageTimer.setRepeats(false);
		add(next);
		add(prev);
		add(all);
		add(replace);
		add(replaceAll);
		add(close);
		add(message);
		KeyListener keyListener = new KeyListener(){
			public void keyTyped(KeyEvent e) {}
			public void keyReleased(KeyEvent e) {}
			public void keyPressed(KeyEvent e) {
				if (e.getKeyCode()==KeyEvent.VK_ENTER && e.getModifiers()==0) {
					goToNext();
				}
				else if (e.getKeyCode()==KeyEvent.VK_ESCAPE && e.getModifiers()==0) {
					close();
				}
			}
		};
		addKeyListener(keyListener);
		findTextField.addKeyListener(keyListener);
		replaceTextField.addKeyListener(keyListener);
		next.addKeyListener(keyListener);
		prev.addKeyListener(keyListener);
		all.addKeyListener(keyListener);
		replace.addKeyListener(keyListener);
		replaceAll.addKeyListener(keyListener);
		close.addKeyListener(keyListener);
		cellObserver = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				doLayout();
			}
		};
		findLines.addObserver(cellObserver);
		replaceLines.addObserver(cellObserver);
		doLayout();
	}

	public void close() {
		PwnIDE.editor.findAndTabs.remove(FindBar.this);
		PwnIDE.editor.findAndTabs.doLayout();
		CodeFile file = PwnIDE.ui.getCurrentCodeFile();
		if (file!=null) {
			file.getCaretRef().set(file.getCaretRef().get());
		}
	}

	public void doLayout() {
		int width;
		int height = 28;
		int x = 6;
		// Minimum of 64 pixels
		Dimension findDim = findTextField.getMinimumSize();
		findTextField.setBounds(x,height/2-findDim.height/2,findDim.width,findDim.height);
		x += findDim.width+6;		// 4 extra for cell spacing
		Dimension replaceDim = replaceTextField.getMinimumSize();
		replaceTextField.setBounds(x,height/2-findDim.height/2,replaceDim.width,replaceDim.height);
		x += replaceDim.width+4;
		next.setLocation(x,2);
		x += next.getWidth();
		prev.setLocation(x,2);
		x += prev.getWidth()+2;
		all.setLocation(x,2);
		x += all.getWidth()+2;
		replace.setLocation(x,2);
		x += replace.getWidth()+2;
		replaceAll.setLocation(x,2);
		x += replaceAll.getWidth()+2;
		close.setLocation(x,2);
		x += close.getWidth()+2+6;	// 6 extra pixels to separate the message from the buttons
		message.setLocation(x,6);
		x += message.getWidth()+6;

		width = x;
		if (getParent()!=null) {
			width = Math.max(width,getParent().getWidth());
		}

		Dimension dim = new Dimension(width,height);
		setSize(dim);
		setMinimumSize(dim);
		setPreferredSize(dim);
		// For some reason, providing Short.MAX_VALUE indicates that it can be any width, but left-aligns it in a managed layout... *sigh*
		setMaximumSize(new Dimension(Short.MAX_VALUE,height));
	}

	protected void paintComponent(Graphics g) {
		g.setColor(getBackground());
		g.fillRect(0,0,getWidth(),getHeight());
	}

	public void findAll() {
		CodeFile file = PwnIDE.ui.getCurrentCodeFile();
		if (file!=null) {
			ArrayList<TextCaret> carets = new ArrayList<TextCaret>();
			TextCaret textCaret = new TextCaret(null,file.getName(),0,0,0);
			while (true) {
				textCaret = findNextInFile(findTextField.getLines().toString(),textCaret,false);
				if (textCaret==null) {
					break;
				}
				carets.add(textCaret);
			}
			if (carets.size()>0) {
				PwnIDE.editor.showCaretList(carets);
			}
			else {
				message.setText("No occurrences were found.");
				message.setSize(message.getPreferredSize());
				messageTimer.start();
			}
		}
	}

	public void replaceText() {
		CodeFile file = PwnIDE.ui.getCurrentCodeFile();
		if (file!=null) {
			Caret caret = file.getCaretRef().get();
			if (caret instanceof TextCaret) {
				TextCaret textCaret = (TextCaret)caret;
				TextReplacementEvent event = textCaret.getLineList().replace(textCaret.getFirstLine(),textCaret.getFirstLineCol(),textCaret.getLastLine(),textCaret.getLastLineCol(),replaceTextField.getLines().toString());
				PwnIDE.editor.eventDone(event);
				goToNext();
				findTextField.requestFocus();
			}
		}
	}

	public void replaceAllText() {
		CodeFile file = PwnIDE.ui.getCurrentCodeFile();
		if (file!=null) {
			TextCaret textCaret = new TextCaret(null,file.getName(),0,0,0);
			ArrayList<Undoable> events = new ArrayList<Undoable>();
			while (true) {
				textCaret = findNextInFile(findTextField.getLines().toString(),textCaret,false);
				if (textCaret==null) {
					break;
				}
				TextReplacementEvent event = textCaret.getLineList().replace(textCaret.getFirstLine(),textCaret.getFirstLineCol(),textCaret.getLastLine(),textCaret.getLastLineCol(),replaceTextField.getLines().toString());
				events.add(event);
				file.getCaretRef().set(textCaret);
			}
			findTextField.requestFocus();
			String description = "Replaced "+events.size()+" occurrence"+(events.size()!=1 ? "s":"");
			message.setText(description);
			message.setSize(message.getPreferredSize());
			messageTimer.start();
			PwnIDE.editor.eventDone(new UndoSet(events,description));
		}
	}

	public void goToNext() {
		CodeFile file = PwnIDE.ui.getCurrentCodeFile();
		if (file!=null) {
			Caret caret = file.getCaretRef().get();
			if (caret instanceof TextCaret) {
				Caret newCaret = findNextInFile(findTextField.getLines().toString(),((TextCaret)caret));
				if (newCaret!=null) {
					file.getCaretRef().set(newCaret);
				}
				findTextField.requestFocus();
			}
		}
	}
	public void goToPrev() {
		CodeFile file = PwnIDE.ui.getCurrentCodeFile();
		if (file!=null) {
			Caret caret = file.getCaretRef().get();
			if (caret instanceof TextCaret) {
				Caret newCaret = findPrevInFile(findTextField.getLines().toString(),((TextCaret)caret));
				if (newCaret!=null) {
					file.getCaretRef().set(newCaret);
				}
				findTextField.requestFocus();
			}
		}
	}
	public TextCaret findNextInFile(String text, TextCaret caret) {
		return findNextInFile(text,caret,true);
	}
	public TextCaret findNextInFile(String text, TextCaret caret,boolean wrap) {
		if (caret==null) {
			throw new NullPointerException("Why is the search caret null in findNextInFile?");
		}
		LineList list = caret.getLineList();
		CodeFile file = list.getParent().getFile();
		// Search from one character later
		int line = caret.getFirstLine();
		int col = caret.getFirstLineCol();
		if (col==list.get(line).length()) {
			col = 0;
			++line;
		}
		else {
			++col;
		}
		LineList startList = list;
		int startLine = line;
		int startCol = col;
		boolean hasLeftFirstLine = false;
		while (true) {
			while (line<list.size()) {
				int index;
				if ((index = list.get(line).indexOf(text,col))>=0) {
					if (list==startList && line==startLine && hasLeftFirstLine && index>=startCol) {
						return null;
					}
					return new TextCaret(null,list,line,index,line,index+text.length(),0);
				}
				if (list==startList && line==startLine && hasLeftFirstLine) {
					return null;
				}
				col = 0;
				++line;
				hasLeftFirstLine = true;
			}
			line = 0;
			list = CodeFileChunk.getNextLineListPlus(list);
			if (list==null) {
				if (wrap) {
					list = file.getFirstLineList();
				}
				else {
					return null;
				}
			}
		}
	}
	public TextCaret findPrevInFile(String text, TextCaret caret) {
		if (caret==null) {
			throw new NullPointerException("Why is the search caret null in findPrevInFile?");
		}
		LineList list = caret.getLineList();
		CodeFile file = list.getParent().getFile();
		// Search from one character earlier
		int line = caret.getFirstLine();
		int col = caret.getFirstLineCol();
		if (col==0) {
			if (line!=0) {
				--line;
			}
			else {
				list = CodeFileChunk.getPrevLineListPlus(list);
				if (list==null) {
					list = file.getLastLineListPlus();
				}
				line = list.size()-1;
				col = list.get(line).length();
			}
		}
		else {
			--col;
		}
		LineList startList = list;
		int startLine = line;
		int startCol = col;
		boolean hasLeftFirstLine = false;
		while (true) {
			while (line>=0) {
				int index;
				if ((index = list.get(line).lastIndexOf(text,col))>=0) {
					if (list==startList && line==startLine && hasLeftFirstLine && index<=startCol) {
						return null;
					}
					return new TextCaret(null,list,line,index,line,index+text.length(),0);
				}
				if (list==startList && line==startLine && hasLeftFirstLine) {
					return null;
				}
				--line;
				hasLeftFirstLine = true;
				if (line>=0) {
					col = list.get(line).length();
				}
			}
			list = CodeFileChunk.getPrevLineListPlus(list);
			if (list==null) {
				list = file.getLastLineListPlus();
			}
			line = list.size()-1;
			col = list.get(line).length();
		}
	}

	public CaretRef getCaretRef() {
		return caretRef;
	}
}
